#include "eeprom.h"

#if (EE_USE_DEBUG == pdON)
#include "stdio_uc.h"
#endif

#if (EEPROM_USE_DEVIO == pdON)
static int mem_WriteBlock(u8* data, u32 addr, u16 len);
static int mem_ReadBlock(u8* data, u32 addr, u16 len);
#endif

void eeprom_Init(void) {
	//inicia a memoria eeprom
	u32 val;
	EEPWRDWN &= ~PWRDWN;
	//	EEPROM is automate turn on after reset Setting clock:
	//	EEPROM required a 375kHz. This clock is generated by dividing the system bus clock.
	val = (CCLK/375000)-1;
	EECLKDIV = val;

	// Setting wait state
	val  = ((((CCLK / 1000000) * 15) / 1000) + 1);
	val |= (((((CCLK / 1000000) * 55) / 1000) + 1) << 8);
	val |= (((((CCLK / 1000000) * 35) / 1000) + 1) << 16);
	EEWSTATE = val;

	#if (EE_USE_DEBUG == pdON)
	plog("EE: EECLKDIV[0x%x]= %d [0x%x]"CMD_TERMINATOR, &EECLKDIV, EECLKDIV, EECLKDIV);
	plog("EE: EEWSTATE[0x%x]= %d [0x%x]"CMD_TERMINATOR, &EEWSTATE, EEWSTATE, EEWSTATE);
	#endif
}

// @brief 		Write data to EEPROM at specific address
// @param[in]	address EEPROM address that start to write data, it must be
// 				in range 0..0x1000
// 				mode	Write mode, should be:
// 					- MODE_8_BIT	: write 8 bit mode
// 					- MODE_16_BIT	: write 16 bit mode
// 					- MODE_32_BIT	: write 32 bit mode
// 				data	buffer that contain data that will be written to buffer
// 				count	number written data
// @return 		None
// @note		This function actually write data into EEPROM memory and automatically
// 				write into next page if current page is overflowed
 void eeprom_Write(u16 page_offset, u16 page_address, void* data, tEE_ModeType mode, u32 count) {
	u32 i;
	u8* p8 = (u8*)data;
	u16* p16 = (u16*)data;
	u32* p32 = (u32*)data;

	EEINTSTATCLR = ((1 << EEPROM_ENDOF_RW)|(1 << EEPROM_ENDOF_PROG));
	//check page_offset
	if(mode == MODE_16_BIT){
		// TODO - while (1)???
		if((page_offset & 0x01)!=0) while(1);
	}
	else if(mode == MODE_32_BIT) {
	 	// TODO - while (1)???
	 	if((page_offset & 0x03)!=0) while(1);
	}

	EEADDR = EEPROM_PAGE_OFFSET(page_offset);
	EEINTSTATCLR = (1 << EEPROM_ENDOF_RW);

	for(i=0; i<count; i++) {
		//update data to page register
		if(mode == MODE_8_BIT){
			EECMD = EEPROM_CMD_8_BIT_WRITE;
			//EEWDATA = *(((u8 *)data)++);
			EEWDATA = *p8++;
			page_offset +=1;
		} else if(mode == MODE_16_BIT) {
			EECMD = EEPROM_CMD_16_BIT_WRITE;
			//EEWDATA = *(((u16 *)data)++);
			EEWDATA = *p16++;
			page_offset +=2;
		} else {
			EECMD = EEPROM_CMD_32_BIT_WRITE;
			//EWDATA = *(((u32 *)data)++);
			EEWDATA = *p32++;
			page_offset +=4;
		}

		if((page_offset >= EEPROM_PAGE_NUM)|(i==count-1)) {
			//update to EEPROM memory
			EEINTSTATCLR = (0x1 << EEPROM_ENDOF_PROG);
			EEADDR = EEPROM_PAGE_ADRESS(page_address);
			EECMD = EEPROM_CMD_ERASE_PRG_PAGE;
			while(!((EEINTSTAT >> 28)&0x01));
		}

		if(page_offset >= EEPROM_PAGE_NUM) {
			page_offset = 0;
			page_address +=1;
			EEADDR =0;
			if(page_address > EEPROM_PAGE_NUM - 1) page_address = 0;
		}
	}
}

// @brief 		Read data to EEPROM at specific address
// @param[in]
// 				data	buffer that contain data that will be written to buffer
//				mode	Read mode, should be:
// 					- MODE_8_BIT	: write 8 bit mode
// 					- MODE_16_BIT	: write 16 bit mode
// 					- MODE_32_BIT	: write 32 bit mode
// 				size	number read data (bytes)
// @return 		data	buffer that contain data that will be read to buffer
void eeprom_Read(u16 page_offset, u16 page_address, void* data, tEE_ModeType mode, u32 count) {
    u32 i;
   	u8* p8 = (u8*)data;
	u16* p16 = (u16*)data;
	u32* p32 = (u32*)data;


	EEINTSTATCLR = ((1 << EEPROM_ENDOF_RW)|(1 << EEPROM_ENDOF_PROG));
	EEADDR = EEPROM_PAGE_ADRESS(page_address)|EEPROM_PAGE_OFFSET(page_offset);

	if( mode == MODE_8_BIT)
		EECMD = EEPROM_CMD_8_BIT_READ|EEPROM_CMD_RDPREFETCH;
	else if( mode == MODE_16_BIT){
		EECMD = EEPROM_CMD_16_BIT_READ|EEPROM_CMD_RDPREFETCH;
		//check page_offset
		if ( (page_offset &0x01)!=0)
			// TODO - while (1)???
			while(1);
	} else {
		EECMD = EEPROM_CMD_32_BIT_READ|EEPROM_CMD_RDPREFETCH;
		//page_offset must be a multiple of 0x04
		if((page_offset & 0x03)!=0)
			// TODO - while (1)???
			while(1);
	}

	//read and store data in buffer
	for(i=0; i<count; i++){
		 EEINTSTATCLR = (1 << EEPROM_ENDOF_RW);
		 if(mode == MODE_8_BIT){
		 	//*(((u8 *)data)++) = (u8)(EERDATA);
		 	*p8++ = (u8)(EERDATA);

			 page_offset +=1;
		 } else if (mode == MODE_16_BIT)  {
			 //*(((u16 *)data)++) = (u16)(EERDATA);
			 *p16++ = (u8)(EERDATA);
			 page_offset +=2;
		 } else {
			 //*(((u32 *)data)++) = (u32)(EERDATA);
			 *p32++ = (u8)(EERDATA);
			 page_offset +=4;
		 }

		 while(!((EEINTSTAT >> 26)&0x01));

		 if( page_offset >= EEPROM_PAGE_SIZE ) {
			 page_offset = 0;
			 page_address++;
			 EEADDR = EEPROM_PAGE_ADRESS(page_address)|EEPROM_PAGE_OFFSET(page_offset);
			 if(mode == MODE_8_BIT)
			 	EECMD = EEPROM_CMD_8_BIT_READ|EEPROM_CMD_RDPREFETCH;
			 else if(mode == MODE_16_BIT)
				EECMD = EEPROM_CMD_16_BIT_READ|EEPROM_CMD_RDPREFETCH;
			 else
			 	EECMD = EEPROM_CMD_32_BIT_READ|EEPROM_CMD_RDPREFETCH;
		 }
	}
}


// @brief 		Erase to EEPROM at specific address
// @param[in]	address EEPROM address that start to read data, should be
// 				in range: 0..4096
// 				mode	Read mode, should be:
// 					- MODE_8_BIT	: write 8 bit mode
// 					- MODE_16_BIT	: write 16 bit mode
// 					- MODE_32_BIT	: write 32 bit mode
// 				size	number read data
// 					- byte unit in MODE_8_BIT
// 					- word unit in MODE_16_BIT
// 					- double word unit in MODE_32_BIT
// @return 		data	buffer that contain data that will be read to buffer
void eeprom_Erase(u32 address) {
	u32 i;
	//clear page register
	EECMD = EEPROM_CMD_8_BIT_WRITE;
	for(i=0;i<64;i++)
	{
		EEWDATA = 0;
		while(!((EEINTSTAT >> 26)&0x01));
	}

	EEINTSTATCLR = (0x1 << EEPROM_ENDOF_PROG);
	EEADDR = EEPROM_PAGE_ADRESS(address);
	EECMD = EEPROM_CMD_ERASE_PRG_PAGE;
	while(!((EEINTSTAT >> 28)&0x01));
}

// @brief 		Enable/Disable EEPROM power down mdoe
// @param[in]	NewState	PowerDown mode state, should be:
// 					- ENABLE: Enable power down mode
// 					- DISABLE: Disable power down mode
// @return 		None
void eeprom_PowerDown(u8 NewState) {
	if(NewState == pdON)		EEPWRDWN |= PWRDWN;
	else						EEPWRDWN &= ~PWRDWN;
}

// ###########################################################################################
// DEVIO
#if (EEPROM_USE_DEVIO == pdON)
static int mem_WriteBlock(u8* data, u32 addr, u16 len) {
	eeprom_Write(addr%64, addr/64, data, MODE_8_BIT, len);
	#if (EE_USE_DEBUG == pdON)
	plog("EE: write addr init[0x%x] len %d"CMD_TERMINATOR, addr, len);
	#endif

	return pdPASS;
}

static int mem_ReadBlock(u8* data, u32 addr, u16 len) {
	eeprom_Read(addr%64, addr/64, data, MODE_8_BIT, len);
	#if (EE_USE_DEBUG == pdON)
	plog("EE: read addr init[0x%x] len %d"CMD_TERMINATOR, addr, len);
	#endif

	return pdPASS;
}

int devio_eeprom(u8 op, void* data, u32 addr, u32 len) {
	int r = pdFAIL;
    switch (op) {
        case doREAD	: r = mem_ReadBlock(data, addr, len);     break;
        case doWRITE: r = mem_WriteBlock(data, addr, len);    break;
        default: break;
    }

	#if (EE_USE_DEBUG == pdON)
    plog("EE: devio ret %d"CMD_TERMINATOR, r);
	#endif

    return r;
}
#endif
